/*----------------------------------------------------------
| File      : netlib.c
| Author    : Simon Amor <simon@foobar.net>
| WWW       : http://www.foobar.net/~simon/
| Version   : 0.5
|
| Description
| ~~~~~~~~~~~
| A library which allows users to write network applications
| without too much effort. Suggestions for new functions are
| always welcome.
|
*-----------------------------------------------------------*/

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <netdb.h>
#include <sys/file.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>               /* for sysconf */
#include <stdarg.h>
/* malloc() and free() */
#include <malloc.h>
/* strcasecmp() */
#include <string.h>

#include <netlib.h>

/*****************************************************************************
 * a pointer to a static version id
 ***/
char *NetVersion = "netlib v0.5 by Simon Amor";

/*****************************************************************************
 * Closes a socket - 'close(fd)' can also be used if preferred
 * Returns -1 if error or 0 if successful (just a #define)
 ***/
#define NetClose(x)   close(x)

/*****************************************************************************
 * Returns an address for output - tries to convert to name if possible.
 * #include <arpa/inet.h>
 ***/
char *NetAddrOut(struct in_addr address)
{
  extern char *inet_ntoa();
  struct hostent *he;

  if ((he=gethostbyaddr((char *)&address.s_addr,sizeof(address.s_addr),AF_INET))!=NULL)
    return he->h_name;
  else
    return (inet_ntoa(address));
}

/*****************************************************************************
 * Accept a new connection from the master socket
 * returns info about connection in addr & int is
 * the fd of the new connection
 ***/
int NetOpenConnection(int mastersocket,struct sockaddr_in *addr)
{
  extern char *inet_ntoa();
  int     newsock;
  int     addr_len;

  addr_len=sizeof(struct sockaddr);
  newsock=accept(mastersocket,(void *)addr,&addr_len);
  if ((newsock==-1) && (errno==EWOULDBLOCK))
    return -1;

  return newsock;
}

/*****************************************************************************
 * Set up master socket and set non blocking
 * Returns negative number if error, else fd
 * The port must be >1024 (and usually =<9999)
 ***/
int NetCreateMasterSocket(int port)
{
  int opensock;
  int opt=1;
  struct sockaddr_in server;

  server.sin_family = AF_INET;
  server.sin_addr.s_addr = INADDR_ANY ;
  server.sin_port = htons(port);

  if ((opensock=socket(AF_INET,SOCK_STREAM,0))<0)
    return -1;

  if (setsockopt(opensock,SOL_SOCKET,SO_REUSEADDR,(char *) &opt,sizeof(opt))<0)
    return -2;

  if (bind(opensock,(struct sockaddr *)&server,sizeof(server)))
    return -3;

  if (listen(opensock, 5)<0)
    return -4;

  return opensock;
}

/*****************************************************************************
 * Set a socket non-blocking
 * returns -1 on failure, 0 on success
 ***/
int NetNonBlocking(int socket)
{
  if (fcntl(socket,F_SETFL,O_NDELAY)==-1)
    return (-1);
  return (0);
}

/*****************************************************************************
 * Send a string to a socket
 * if failed, returns -1 else returns the number of bytes sent
 ***/
int NetPutString(int socket, char *format, ...)
{
  va_list ap;
  char *string = (char *)malloc(4096);
  int retval;
  
  if (string == NULL)
    return -1;
    
  va_start(ap, format);
  if (!format || !*format) 
    return -1;

  vsnprintf(string, 4096, format, ap);
  va_end(ap);
  retval = send(socket,string,strlen(string),0);
  free(string);
  return retval;
}


/*****************************************************************************
 * Receive stuff from the socket
 * if failed, returns -1, else returns bytes received
 ***/
int NetGetString(int socket,char *buffer,int buffer_size) 
{
  int count;

  if ((count=recv(socket,buffer,buffer_size,0))==-1)
  {
    if (errno==EWOULDBLOCK)
    {
      buffer[0]='\0';
      return 0;
    }
    else
      return -1;
  }
  buffer[count]='\0';
  return count;
}

/*****************************************************************************
 * Receive a single line of data from the socket (up to a '\n' - not '\r')
 * if failed, returns -1, else returns bytes received
 ***/
int NetGetLine(int socket,char *buffer,int buffer_size)
{
  int count=0;

  do 
  {
    if (recv(socket,&buffer[count],1,0) == -1)
    {
      if (errno==EWOULDBLOCK)
      {
        buffer[0]='\0';
        return 0;
      }
      else
        return -1;
    }
    count++;
  } while (buffer[count-1] != '\n');

  buffer[count]='\0';
  return count;
}

/*****************************************************************************
 * Connects to <host> on port <port>
 * returns file descriptor attached to the specified port
 * -1 = no socket, -2 = no host, -3 = no connect
 ***/
int NetConnectTo(char *host,int port)
{
  struct hostent    *he;
  struct sockaddr_in server;
  int                connect_fd;

  if ((connect_fd=socket(AF_INET,SOCK_STREAM,0))<0)
    return (-1);

  if ((he=gethostbyname(host))==NULL)
  {
    close(connect_fd);
    return (-2);
  }

  memcpy(&server.sin_addr,he->h_addr,he->h_length);
  server.sin_family=AF_INET;
  server.sin_port=htons(port);

  if (connect(connect_fd,(struct sockaddr *)&server,sizeof(server))<0)
  {
    close(connect_fd);
    return (-3);
  }
  return connect_fd;
}

/*****************************************************************************
 * Returns the maximum connections allowed by the hardware
 ***/
int NetMaxConnections(void)
{
  return (sysconf(_SC_OPEN_MAX));
}

/*****************************************************************************
 * NetGetLogin :  returns UID of connection
 *
 * If returns a NULL for UID, connection failed, check return value.
 * Else, UID points to a static buffer containing UID
 * Return values:
 *   0 = ok
 *  -1 = socket failure
 *  -2 = unknown host
 *  -3 = information unobtainable - possibly no ident server
 *  -4 = invalid ports specified
 *  -5 = malloc failed
 *  -6 = invalid reply from server
 ***/
int NetGetLogin(char *RemoteHost, int LocalPort, int RemotePort, char **uid)
{
  int query_fd, params;
  static char uidBuffer[64];
  char *buffer;
  char reply_type[81], opsys_or_error[81];
  char identifier[1024];

  *uid = uidBuffer;

  for (params = 0; params < 64; params++)
    uidBuffer[params] = '\0';

  if ((query_fd = NetConnectTo(RemoteHost, 113))<0)
    return(query_fd);

  printf("Hostname: %s\n", RemoteHost);

  if ((buffer=(char *)malloc(1024)) == NULL)
    return (-5);

  sprintf(buffer,"%d, %d\n", LocalPort, RemotePort);
  if (NetPutString(query_fd, buffer) < 0)
  {
    free(buffer);
    return (-1);
  }
  if (NetGetString(query_fd, buffer, 1020) < 0)
  {
    free(buffer);
    return (-1);
  }
  NetClose(query_fd);

  params = sscanf(buffer, " %d , %d : %[^ \t\n\r:] : %[^\t\n\r:] : %[^\n\r]",
		&LocalPort, &RemotePort, reply_type, opsys_or_error, identifier);

  printf("Buffer: %s\n", buffer);

  free(buffer);
  if (params < 3)
    return (-3);

  if ((strcasecmp(reply_type, "ERROR") == 0) &&
      ((strcasecmp(opsys_or_error, "INVALID-PORT") == 0) ||
        (strcasecmp(opsys_or_error, "NO-USER") == 0)))
      return (-4);

  if (strcasecmp(reply_type, "USERID") != 0)
    return (-6);

  identifier[63]='\0';
  strcpy(uidBuffer, identifier);
  return(0);
}
